﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Drawing;

namespace System.Windows.Forms;

/// <summary>
///  Cache of GDI+ objects to reuse commonly created items.
/// </summary>
internal static class GdiPlusCache
{
    // Keeping these as ThreadStatic for now. With testing, and if we can make them immutable ourselves (needs
    // additional System.Drawing functionality), we can look at making this shared. Adding locking, however, does
    // add cost to retrieving entries so it might not be worth it.
    [ThreadStatic]
    private static PenCache? s_penCache;
    [ThreadStatic]
    private static SolidBrushCache? s_brushCache;

    // Picking soft and hard limits expecting that 30 of each is a reasonable upper limit of permanently reserved
    // space. These items are roughly .5KB each, with _most_ of the memory being native (in the GDI+ object).

    private static PenCache PenCache => s_penCache ??= new(softLimit: 30, hardLimit: 40);
    private static SolidBrushCache BrushCache => s_brushCache ??= new(softLimit: 30, hardLimit: 40);

    private static PenCache.Scope GetPenScope(Color color)
    {
        if (color.IsKnownColor)
        {
            Pen? pen = color.IsSystemColor
                ? SystemPens.FromSystemColor(color)
                : PenFromKnownColor(color.ToKnownColor());

            if (pen is not null)
            {
                return new PenCache.Scope(pen);
            }
        }

        return PenCache.GetEntry(color).CreateScope();
    }

    private static SolidBrushCache.Scope GetSolidBrushScope(Color color)
    {
        if (color.IsKnownColor)
        {
            SolidBrush? solidBrush = color.IsSystemColor
                ? (SolidBrush?)SystemBrushes.FromSystemColor(color)
                : (SolidBrush?)BrushFromKnownColor(color.ToKnownColor());

            if (solidBrush is not null)
            {
                return new SolidBrushCache.Scope(solidBrush);
            }
        }

        return BrushCache.GetEntry(color).CreateScope();
    }

    /// <summary>
    ///  Returns a cached <see cref="Pen"/>. Use in a using and assign to var.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   Correct: using var pen = GdiPlusCache.GetCachedPen(Color.Blue);
    ///   Incorrect (LEAKS): using Pen pen = GdiPlusCache.GetCachedPen(Color.Blue);
    ///  </para>
    ///  <para>
    ///   Debug builds track proper disposal.
    ///  </para>
    /// </remarks>
    internal static PenCache.Scope GetCachedPenScope(this Color color) => GetPenScope(color);

    /// <inheritdoc cref="GetCachedPenScope(Color)"/>
    internal static PenCache.Scope GetCachedPenScope(this Color color, int width)
        => width == 1 ? GetPenScope(color) : new PenCache.Scope(new Pen(color, width));

    /// <inheritdoc cref="GetCachedPenScope(Color)"/>
    internal static SolidBrushCache.Scope GetCachedSolidBrushScope(this Color color) => GetSolidBrushScope(color);

    private static Brush? BrushFromKnownColor(KnownColor color) => color switch
    {
        // Starting with the expected most common
        KnownColor.Black => Brushes.Black,
        KnownColor.White => Brushes.White,
        KnownColor.Gray => Brushes.Gray,
        KnownColor.Red => Brushes.Red,
        KnownColor.Green => Brushes.Green,
        KnownColor.Blue => Brushes.Blue,
        KnownColor.Yellow => Brushes.Yellow,
        KnownColor.Brown => Brushes.Brown,
        KnownColor.LightGray => Brushes.LightGray,
        KnownColor.LightGreen => Brushes.LightGreen,
        KnownColor.LightBlue => Brushes.LightBlue,
        KnownColor.LightYellow => Brushes.LightYellow,
        KnownColor.DarkGray => Brushes.DarkGray,
        KnownColor.DarkRed => Brushes.DarkRed,
        KnownColor.DarkGreen => Brushes.DarkGreen,
        KnownColor.DarkBlue => Brushes.DarkBlue,
        KnownColor.Transparent => Brushes.Transparent,
        _ => null
    };

    private static Pen? PenFromKnownColor(KnownColor color) => color switch
    {
        // Starting with the expected most common
        KnownColor.Black => Pens.Black,
        KnownColor.White => Pens.White,
        KnownColor.Gray => Pens.Gray,
        KnownColor.Red => Pens.Red,
        KnownColor.Green => Pens.Green,
        KnownColor.Blue => Pens.Blue,
        KnownColor.Yellow => Pens.Yellow,
        KnownColor.Brown => Pens.Brown,
        KnownColor.LightGray => Pens.LightGray,
        KnownColor.LightGreen => Pens.LightGreen,
        KnownColor.LightBlue => Pens.LightBlue,
        KnownColor.LightYellow => Pens.LightYellow,
        KnownColor.DarkGray => Pens.DarkGray,
        KnownColor.DarkRed => Pens.DarkRed,
        KnownColor.DarkGreen => Pens.DarkGreen,
        KnownColor.DarkBlue => Pens.DarkBlue,
        KnownColor.Transparent => Pens.Transparent,
        _ => null
    };
}
